/*
 * SonarLint for IntelliJ IDEA
 * Copyright (C) 2015-2025 SonarSource
 * sonarlint@sonarsource.com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
 */
package org.sonarlint.intellij.ui.vulnerabilities.tree

import com.intellij.openapi.application.ModalityState
import org.sonarlint.intellij.finding.FindingType
import org.sonarlint.intellij.finding.Flow
import org.sonarlint.intellij.finding.FragmentLocation
import org.sonarlint.intellij.finding.SameFileFlowFragment
import org.sonarlint.intellij.finding.issue.vulnerabilities.LocalTaintVulnerability
import org.sonarlint.intellij.ui.UiUtils.Companion.runOnUiThread
import org.sonarlint.intellij.ui.nodes.SummaryNode
import org.sonarlint.intellij.ui.tree.CompactTree
import org.sonarlint.intellij.ui.tree.CompactTreeModel
import org.sonarlint.intellij.ui.tree.NodeRenderer
import org.sonarlint.intellij.ui.tree.TreeSummary
import org.sonarlint.intellij.ui.vulnerabilities.tree.filter.FindingFilter
import org.sonarlint.intellij.ui.vulnerabilities.tree.filter.FocusFilter
import org.sonarlint.intellij.ui.vulnerabilities.tree.filter.ResolutionFilter
import org.sonarlint.intellij.ui.vulnerabilities.tree.render.FileRenderer
import org.sonarlint.intellij.ui.vulnerabilities.tree.render.FlowRenderer
import org.sonarlint.intellij.ui.vulnerabilities.tree.render.LocalTaintVulnerabilityRenderer
import org.sonarlint.intellij.ui.vulnerabilities.tree.render.LocationRenderer
import org.sonarlint.intellij.ui.vulnerabilities.tree.render.SameFileFlowFragmentRenderer

class TaintVulnerabilityTreeUpdater(private val treeSummary: TreeSummary) {
    val model = CompactTreeModel(SummaryNode(treeSummary))

    val renderer = NodeRenderer<Any> { renderer, node ->
        when (node) {
            is SummaryNode -> node.render(renderer)
            is FileSummary -> FileRenderer.render(renderer, node)
            is LocalTaintVulnerability -> LocalTaintVulnerabilityRenderer.render(renderer, node)
            is Flow -> FlowRenderer.render(renderer, node)
            is SameFileFlowFragment -> SameFileFlowFragmentRenderer.render(renderer, node)
            is FragmentLocation -> LocationRenderer.render(renderer, node)
        }
    }

    var focusFilter: FocusFilter = FocusFilter.ALL_CODE
        set(value) {
            field = value
            applyFiltering()
        }

    var resolutionFilter: ResolutionFilter = ResolutionFilter.OPEN_ONLY
        set(value) {
            field = value
            applyFiltering()
        }

    private fun findingFilters() = listOf<FindingFilter>(focusFilter, resolutionFilter)

    var taintVulnerabilities: List<LocalTaintVulnerability> = mutableListOf()
        set(value) {
            field = value
            applyFiltering()
        }

    var filteredTaintVulnerabilities: List<LocalTaintVulnerability> = emptyList()

    private fun applyFiltering() {
        val filters = findingFilters()
        filteredTaintVulnerabilities = taintVulnerabilities.filter { vulnerability -> filters.all { filter -> filter.filter(vulnerability) } }
        runOnUiThread(ModalityState.defaultModalityState()) { model.setCompactTree(createCompactTree(filteredTaintVulnerabilities)) }
        treeSummary.refresh(filteredTaintVulnerabilities.filter { it.file() != null }.groupBy { it.file() }.keys.size, filteredTaintVulnerabilities.size)
    }

    private fun createCompactTree(taintVulnerabilities: List<LocalTaintVulnerability>): CompactTree {
        val taintVulnerabilitiesByFile = taintVulnerabilities.filter { it.file() != null }.groupBy { it.file()!! }
        val sortedFiles = taintVulnerabilitiesByFile.keys.sortedWith(compareBy({ it.name }, { it.path }))
            .map { FileSummary(it, taintVulnerabilitiesByFile[it]!!.size, FindingType.TAINT_VULNERABILITY) }
        val nodesByParent: MutableMap<Any, List<Any>> = mutableMapOf(model.root to sortedFiles)
        taintVulnerabilitiesByFile.forEach { (file, taintVulnerabilities) ->
            val fileSummary = FileSummary(file, taintVulnerabilities.size, FindingType.TAINT_VULNERABILITY)
            val sortedTaintVulnerabilities = taintVulnerabilities.sortedWith(compareBy<LocalTaintVulnerability> { it.creationDate() }.thenByDescending { it.severity() }.thenBy { it.rangeMarker()?.startOffset }.thenBy { it.getRuleKey() })
            nodesByParent[fileSummary] = sortedTaintVulnerabilities
            sortedTaintVulnerabilities.forEach { taintVulnerability ->
                nodesByParent[taintVulnerability] = taintVulnerability.flows
                taintVulnerability.flows.forEach { flow ->
                    if (flow.isCrossFileFlow) {
                        nodesByParent[flow] = flow.crossFileFlowFragments
                        flow.crossFileFlowFragments.forEach { fragment ->
                            nodesByParent[fragment] = fragment.locations
                            fragment.locations.forEach { location -> nodesByParent[location] = emptyList() }
                        }
                    } else {
                        val singleFileFlow = flow.crossFileFlowFragments[0]
                        nodesByParent[flow] = singleFileFlow.locations
                        singleFileFlow.locations.forEach { location -> nodesByParent[location] = emptyList() }
                    }
                }
            }
        }
        return CompactTree(nodesByParent)
    }
}
